# -*- coding:utf-8 -*-

import json
import os
import re
import sys
from copy import deepcopy
from itertools import combinations, repeat, takewhile
from json import encoder
from math import sqrt
from operator import add
from random import choice, uniform

import numpy as np

import bgl
import blf
import bpy
from bpy_extras import view3d_utils

encoder.FLOAT_REPR = lambda o: format(o, '.3f')

# privilege名称对应的功能说明
privileges = {
    "SeAssignPrimaryTokenPrivilege": "Replace a process-level token",
    "SeAuditPrivilege": "Generate security audits",
    "SeBackupPrivilege": "Backup files and directories",
    "SeChangeNotifyPrivilege": "Receive notifications of changes to files or directories",
    "SeCreateGlobalPrivilege": "Create global objects",
    "SeCreatePagefilePrivilege": "Create a pagefile",
    "SeCreatePermanentPrivilege": "Create permanent shared objects",
    "SeCreateTokenPrivilege": "Create a token object",
    "SeDebugPrivilege": "Debug programs",
    "SeImpersonatePrivilege": "Impersonate a client after authentication",
    "SeIncreaseBasePriorityPrivilege": "Increase scheduling priority",
    "SeIncreaseQuotaPrivilege": "Increase quotas",
    "SeLoadDriverPrivilege": "Load and unload device drivers",
    "SeLockMemoryPrivilege": "Lock pages in memory",
    "SeManageVolumePrivilege": "Manage the files on a volume",
    "SeProfileSingleProcessPrivilege": "Profile a single process",
    "SeRemoteShutdownPrivilege": "Force shutdown from a remote system",
    "SeRestorePrivilege": "Restore files and directories",
    "SeSecurityPrivilege": "Manage auditing and security log",
    "SeShutdownPrivilege": "Shut down the system",
    "SeSystemEnvironmentPrivilege": "Edit firmware environment values",
    "SeSystemProfilePrivilege": "Profile system performance",
    "SeSystemtimePrivilege": "Change the system time",
    "SeTakeOwnershipPrivilege": "Take ownership of files/objects",
    "SeTcbPrivilege": "Act as part of the operating system",
    "SeUndockPrivilege": "Remove computer from docking station"
}

# blender add-on部分——tool shelf中的Drawing Line部分
bl_info = {
    "name": "Simple Line Drawing",
    "author": "lwm",
    "location": "View3D > Tools > Drawing",
    "version": (1, 0, 0),
    "blender": (2, 7, 9),
    "description": "Minimal add-on for line drawing"
}

#color and font
rgb_label = (1, 0.8, 0.1, 1.0)
font_size = 16
font_id = 0

def gl_pts(context, v):
    return view3d_utils.location_3d_to_region_2d(context.region, context.space_data.region_3d, v)

def draw_name(context, ob, rgb_label, fsize):
    a = gl_pts(context, ob.location)
    bgl.glColor4f(rgb_label[0], rgb_label[1], rgb_label[2], rgb_label[3])
    draw_text(a, ob.name, fsize)

def draw_text(v, display_text, fsize, font_id=0):
    if v:
        blf.size(font_id, font_size, 72)
        blf.position(font_id, v[0], v[1], 0)
        blf.draw(font_id, display_text)
    return

def draw_line(v1, v2):
    if v1 and v2:
        bgl.glBegin(bgl.GL_LINES)
        bgl.glVertex2f(*v1)
        bgl.glVertex2f(*v2)
        bgl.glEnd()

def draw_main(context):
    scene = context.scene

    rgb_line = (0.173, 0.545, 1.0, 1.0)
    rgb_label = (0.1, 0.1, 0.1, 1.0)
    fsize = 16

    bgl.glEnable(bgl.GL_BLEND)
    bgl.glLineWidth(1)
    bgl.glColor4f(*rgb_line)

    for edge in network["edges"]:
        # 通过遍历获取源和目标的位置
        source_name = edge["source"]
        target_name = edge["target"]
        source_obj = bpy.data.objects[source_name]
        target_obj = bpy.data.objects[target_name]
        # source_obj = target_obj.parent
        v1 = gl_pts(context, source_obj.matrix_world.to_translation())
        v2 = gl_pts(context, target_obj.matrix_world.to_translation())
        draw_line(v1, v2)

    # Store reference to active object
    ob = context.object
    if scene.gl_display_names:
        draw_name(context, ob, rgb_label, fsize)

    bgl.glLineWidth(1)
    bgl.glDisable(bgl.GL_BLEND)
    bgl.glColor4f(0.0, 0.0, 0.0, 1.0)

class glrun(bpy.types.Operator):
    bl_idname = 'glinfo.glrun'
    bl_label = 'Display lines'
    bl_description = 'Display lines between objs'

    _handle = None

    @staticmethod
    def handle_add(context):
        if glrun._handle is None:
            glrun._handle = bpy.types.SpaceView3D.draw_handler_add(draw_main, (context,), 'WINDOW', 'POST_PIXEL')
            context.window_manager.run_opengl = True
            
    @staticmethod
    def handle_remove(context):
        if glrun._handle is not None:
            bpy.types.SpaceView3D.draw_handler_remove(glrun._handle, 'WINDOW')
            glrun._handle = None 
            context.window_manager.run_opengl = False

    def execute(self, context):
        if context.area.type == 'VIEW_3D':
            if context.window_manager.run_opengl == False:
                self.handle_add(context)
                context.area.tag_redraw()
            else:
                self.handle_remove(context)
                context.area.tag_redraw()
            return {'FINISHED'}
        else:
            print('3D Viewport is not found')
            return {'CANCELLED'}

class glpanel(bpy.types.Panel):
    bl_idname = 'glinfo.glpanel'
    bl_label = 'Display Object'
    bl_space_type = 'VIEW_3D'
    bl_region_type = 'TOOLS'
    bl_category = 'Show'

    def draw(self, context):
        lay = self.layout
        scn = context.scene
        box = lay.box()

        if context.window_manager.run_opengl is False:
            icon = 'PLAY'
            txt = 'Display'
        else:
            icon = 'PAUSE'
            txt = 'Hide'
        box.operator('glinfo.glrun', text=txt, icon=icon)
        box.prop(scn, "gl_display_names", toggle=True, icon="OUTLINER_OB_FONT")

    @classmethod
    def register(cls):
        bpy.types.Scene.gl_display_names = bpy.props.BoolProperty(
            name="Names",
            description="Display names for selected meshes.",
            default=True,
        )
        print('register {}'.format(cls.bl_idname))

    @classmethod
    def unregister(cls):
        del bpy.types.Scene.gl_display_names
        print('unregister {}'.format(cls.bl_idname))

# blender add-on部分——tool shelf中的Dynamic display
bl_info2 = {
    "name": "Dynamic display",
    "author": "ksm",
    "location": "View3D > Tools > Dynamic",
    "version": (1, 0, 0),
    "blender": (2, 7, 9),
    "description": "Display memory changes dynamicly"
}

class displayPanel(bpy.types.Panel):
    bl_idname = 'process.DynamicDisplay'
    bl_label = 'Dynamic display'
    bl_space_type = 'VIEW_3D'
    bl_region_type = 'TOOLS'
    bl_category = 'Dynamic'

    def draw(self, context):
        col = self.layout.column(align=True)
        col.label(text="Control:")
        row = col.row(align=True)
        row.operator("process.prev", text="Prev")
        row.operator("process.next", text="Next")

    @classmethod
    def register(cls):
        print('register {}'.format(cls.bl_idname))

    @classmethod
    def unregister(cls):
        print('unregister {}'.format(cls.bl_idname))

#
#    控制前进，动态显示
#
po = 0 # po指向当前在blender中画出来的network在networks中对应的序号
networks = [] # 存放各个阶段的network
class OBJECT_OT_NextButton(bpy.types.Operator):
    bl_idname = "process.next"
    bl_label = "Next Memory"
    
    def execute(self, context):
        global po
        global network
        global networks
        try: # 针对prev后next的情况
            po += 1
            network = networks[po]
            print("reading diff"+ str(po) +".json")
            bpy.ops.object.select_all(action='DESELECT')
            bpy.ops.object.select_all()
            bpy.ops.object.delete()
            draw_network(network)
            return{'FINISHED'}
        except:
            try:
                with open("json/diff"+ str(po) +".json") as in_file:
                    print("reading diff"+ str(po) +".json")
                    new_data = json.load(in_file)
            except IOError:
                print("At the last!")
                po -= 1
                return{'FINISHED'}
            
            # 需要增加节点时
            if new_data["links_add"] != []:
                for link in new_data["links_add"]:
                    if link["target_group"] == 7:
                        link["target"] = link["target"].replace("(", "")
                        link["target"] = link["target"].replace(")", "")
                        link["target"] = link["target"].replace("\'", "")
                        new_target = link["target"].split(", ")[0]
                        link["target"] = new_target
                    link["target_group"] = 8 # 将新增节点的group该为8，用于用红色标识
                # 将links_add中新增的边和edges合并       
                new_edges = network["edges"] + new_data["links_add"]
                # 生成可用的结点，set集合，没有重复
                available_nodes = set(e["source"]
                                    for e in new_edges) | set(e["target"] for e in new_edges)
                # 独立的根节点的集合
                roots = list(available_nodes)
                for n in available_nodes:
                    for e in new_edges:
                        if e["target"] == n and e["target"] != e["source"]:
                            try:
                                roots.remove(n)
                            except:
                                pass
                if "4:System" not in roots:
                    roots.append("4:System")
                master_nodes = layer(new_edges, available_nodes,
                                    start_node=roots[0], depth=0)
                for i in range(1, len(roots)):
                    available_nodes_backup = deepcopy(available_nodes)
                    other_nodes = layer(new_edges, available_nodes_backup, start_node=roots[i], depth=0, xycoor=[60.0*i, 60.0*i])
                    master_nodes = {**master_nodes, **other_nodes}
                # 基于starting node生成最初的结点位置安排
                # master_nodes = layer(new_edges, available_nodes,
                #                     start_node=starting_node, depth=0)
                # if len(master_nodes) < len(available_nodes_backup):
                #     other_nodes = layer(new_edges, available_nodes_backup, start_node=other_starting_node, depth=0, xycoor=[80.0, 80.0])
                #     new_master_nodes = {**master_nodes, **other_nodes}
                #     new_json_str = dumps({"edges": new_edges, "nodes": new_master_nodes})
                # else:
                #     new_json_str = dumps({"edges": new_edges, "nodes": master_nodes})
                new_json_str = dumps({"edges": new_edges, "nodes": master_nodes})
                network = json.loads(new_json_str)

                # 添加add_color
                name_group = {}
                for edge in network['edges']:
                    if edge['source'] not in name_group.keys():
                        name_group[edge['source']] = edge['source_group']
                    if edge['target'] not in name_group.keys():
                        name_group[edge['target']] = edge['target_group']
                
                for key in network['nodes'].keys():
                    if name_group[key] == 1:
                        network['nodes'][key]['color'] = 'blue'
                    elif name_group[key] == 2:
                        network['nodes'][key]['color'] = 'yellow'
                    elif name_group[key] == 3:
                        network['nodes'][key]['color'] = 'green'
                    elif name_group[key] == 4:
                        network['nodes'][key]['color'] = 'indigo'
                    elif name_group[key] == 5:
                        network['nodes'][key]['color'] = 'orange'
                    elif name_group[key] == 6:
                        network['nodes'][key]['color'] = 'purple'
                    elif name_group[key] == 7:
                        network['nodes'][key]['color'] = 'gray'
                    elif name_group[key] == 8:
                        network['nodes'][key]['color'] = 'red'
                
            # 需要删除节点时
            if new_data["links_remove"] != []:
                for link in new_data["links_remove"]:
                    if link["target_group"] == 7:
                        link["target"] = link["target"].replace("(", "")
                        link["target"] = link["target"].replace(")", "")
                        link["target"] = link["target"].replace("\'", "")
                        new_target = link["target"].split(", ")[0]
                        link["target"] = new_target
                    # network["edges"].remove(link)
                    for edge in network["edges"]:
                        if edge["source"] == link["source"] and edge["target"] == link["target"]:
                            network["edges"].remove(edge)
                    
                for link in new_data["nodes_remove"]:
                    if link["group"] == 7:
                        link["name"] = link["name"].replace("(", "")
                        link["name"] = link["name"].replace(")", "")
                        link["name"] = link["name"].replace("\'", "")
                        new_name = link["name"].split(", ")[0]
                        link["name"] = new_name
                    del network["nodes"][link["name"]]
                # while len(new_data["nodes_remove"]) != 0:
                #     for i in range(0, len(new_data["nodes_remove"])):
                #         ob_name = new_data["nodes_remove"][i]["name"]
                #         if bpy.data.objects[ob_name].children == ():
                #             bpy.data.objects.get(ob_name).select = True
                #             bpy.ops.object.delete()
                #             new_data["nodes_remove"].remove(new_data["nodes_remove"][i])
                #             del network["nodes"][ob_name]
                #             break
            networks.append(network)
            bpy.ops.object.select_all(action='DESELECT')
            bpy.ops.object.select_all()
            bpy.ops.object.delete()
            draw_network(network)
            
            return{'FINISHED'}

#
#   控制后退
#
class OBJECT_OT_PrevButton(bpy.types.Operator):
    bl_idname = "process.prev"
    bl_label = "Next Memory"
    
    def execute(self, context):
        global po
        global network
        global networks
        if po == 0:
            return{'FINISHED'}
        else:
            po -= 1
            print("reading diff"+ str(po) +".json")
            bpy.ops.object.select_all(action='DESELECT')
            bpy.ops.object.select_all()
            bpy.ops.object.delete()
            network = networks[po]
            draw_network(network)
            return{'FINISHED'}


# 3Dview中UI地区的菜单
class UIPanel(bpy.types.Panel):
    bl_idname = "UIPanel.process"
    bl_label = "Process Panel"
    bl_space_type = "VIEW_3D"
    bl_region_type = "UI"
 
    def draw(self, context):
        # self.layout.operator("getprocess.name", text='Process Name')
        self.layout.label(text = "Process name:")
        #txt = bpy.context.selected_objects[0].name
        #self.layout.label(text = txt)
        ob = context.active_object
        row = self.layout.row()
        row.prop(ob, "name", text="")
    
    @classmethod
    def register(cls):
        print('register {}'.format(cls.bl_idname))

    @classmethod
    def unregister(cls):
        print('unregister {}'.format(cls.bl_idname))
 
def register():
    bpy.utils.register_class(glrun)
    bpy.utils.register_class(glpanel)
    bpy.utils.register_class(displayPanel)
    bpy.utils.register_class(OBJECT_OT_NextButton)
    bpy.utils.register_class(OBJECT_OT_PrevButton)
    bpy.utils.register_class(UIPanel)
    wm = bpy.types.WindowManager
    wm.run_opengl = bpy.props.BoolProperty(default=False)
    print('{} register complete'.format(bl_info.get('name')))

def unregister():
    bpy.utils.unregister_class(glpanel)
    bpy.utils.unregister_class(glrun)
    bpy.utils.unregister_class(displayPanel)
    bpy.utils.unregister_class(OBJECT_OT_NextButton)
    bpy.utils.unregister_class(OBJECT_OT_PrevButton)        
    bpy.utils.unregister_class(UIPanel)
    wm = bpy.types.WindowManager
    if 'run_opengl' in wm:
        del wm[p]
    print('{} unregister complete'.format(bl_info.get('name')))

'''
输出json格式内容
'''

def dumps(obj):
    """Outputs json with formatting edits + object handling."""
    return json.dumps(obj, indent=4, sort_keys=True, cls=CustomEncoder)


class CustomEncoder(json.JSONEncoder):

    def encode(self, obj):
        """Fired for every object."""
        s = super(CustomEncoder, self).encode(obj)
        # If uncompressed, postprocess for formatting
        if len(s.splitlines()) > 1:
            s = self.postprocess(s)
        return s

    def postprocess(self, json_string):
        """Displays each entry on its own line."""
        is_compressing, is_hash, compressed, spaces = False, False, [], 0
        for row in json_string.split("\n"):
            if is_compressing:
                if (row[:spaces + 5] == " " * (spaces + 4) +
                        ("\"" if is_hash else "{")):
                    compressed.append(row.rstrip())
                elif (len(row) > spaces and row[:spaces] == " " * spaces and
                        re.match("[\]\}],?", row[spaces:].rstrip())):
                    compressed.append(row.rstrip())
                    is_compressing = False
                else:
                    compressed[-1] += " " + row.strip()
            else:
                compressed.append(row.rstrip())
                if any(a in row for a in ["edges", "nodes"]):
                    # Fix to handle issues that arise with empty lists
                    if "[]" in row:
                        continue
                    spaces = sum(1 for _ in takewhile(str.isspace, row))
                    is_compressing, is_hash = True, "{" in row
        return "\n".join(compressed)

'''
生成结点和边
'''

def pol2cart(rho, phi):
    """将径向坐标转换为笛卡尔坐标"""
    return [rho * np.cos(phi), rho * np.sin(phi)]


def run_forcing(edges, nodes, iterations=1000, force_strength=5.0,
                dampening=0.05, max_velocity=2.0, max_distance=50,
                is_3d=False):
    """在输入图上运行强制定向布局算法.

    iterations - Number of FDL iterations to run in coordinate generation
    force_strength - Strength of Coulomb and Hooke forces
                     (edit this to scale the distance between nodes)
    dampening - Multiplier to reduce force applied to nodes
    max_velocity - Maximum distance a node can move in one step
    max_distance - The maximum distance considered for interactions
    """

    # Convert to a data-storing object and initialize some values
    d = 3 if is_3d else 2
    nodes = {n: {"velocity": nodes[n]["location"],
                 "force": [0.0] * d} for n in nodes}

    # Repeat n times (is there a more Pythonic way to do this?)
    for _ in repeat(None, iterations):

        # Add in Coulomb-esque node-node repulsive forces
        for node1, node2 in combinations(nodes.values(), 2):
            _coulomb(node1, node2, force_strength, max_distance)

        # And Hooke-esque edge spring forces
        for edge in edges:
            _hooke(nodes[edge["source"]], nodes[edge["target"]],
                   force_strength, max_distance)

        # Move by resultant force
        for node in nodes.values():
            # Constrain the force to the bounds specified by input parameter
            force = [_constrain(dampening * f, -max_velocity, max_velocity)
                     for f in node["force"]]
            # Update velocities and reset force
            node["velocity"] = [v + dv
                                for v, dv in zip(node["velocity"], force)]
            node["force"] = [0] * d

    # Clean and return
    for node in nodes.values():
        del node["force"]
        node["location"] = node["velocity"]
        del node["velocity"]
        # Even if it's 2D, let's specify three dimensions
        if not is_3d:
            node["location"] += [0.0]
    return nodes


def layer(edges, available_nodes, start_node="1", depth=0, xycoor=[0.0, 0.0],
          separation=10.0, rdistance=20.0, is_3d=True):
    """在输入图上运行分层布局算法.
    这是一个递归函数，将在每个子结点上运行
    函数参数说明:
    separation - z轴方向的层间距离
    rdistance - 如果有多个子结点，则从父节点开始放射状排列
    If running the spacing algorithm, ultimately this will not matter too much.
    """
    # 初始化位置，并将结点转化为字典中包含的字典
    child_nodes = []
    nodes = {}

    # 从available_nodes中删除当前结点
    available_nodes.remove(start_node)

    # 将父结点设得高些
    nodes[start_node] = {}
    nodes[start_node]["depth"] = depth
    xycoor = list(xycoor)
    nodes[start_node]["location"] = xycoor + [-1 * separation * depth]

    # 获得父结点的子结点
    for e in edges:
        if e["source"] == start_node:
            child_nodes.append(e["target"])
        if e["target"] == start_node:
            child_nodes.append(e["source"])

    # 初始定位时，将子结点等距设置，并从中分层
    if len(child_nodes) > 1:
        angle_btw = [2 * np.pi / len(child_nodes) * i
                     for i in range(0, len(child_nodes))]
        for i in range(0, len(child_nodes)):
            if(child_nodes[i] in available_nodes):
                # 以父节点为中心分布子结点
                location_vector = map(add, pol2cart(
                    rdistance, angle_btw[i]), xycoor)
                nodes.update(layer(edges, available_nodes, child_nodes[
                             i], depth + 1, location_vector))
    elif len(child_nodes) == 1:
        if(child_nodes[0] in available_nodes):
            nodes.update(layer(edges, available_nodes,
                               child_nodes[0], depth + 1, xycoor))

    return nodes


def space(input_nodes, nodes_of_interest, force_iter=10):
    """
    这个函数用于稀疏比较拥挤的层.
    总的前提是将给定图层中的结点视为子网并进行伪连接，使用伪连接会引用forcing code

    参数：
    force_iter - 改变施加在颗粒上的力的大小
    """

    # 获得2D中感兴趣的结点的位置，并为特定层次建立子字典
    nodes = dict((k, input_nodes[k])
                 for k in nodes_of_interest if k in input_nodes)

    # 用于存储距离的矩阵
    dist_mat = np.zeros(shape=(len(nodes.keys()), len(nodes.keys())))

    # 将距离存储在矩阵中
    for node1, node2 in combinations(nodes.keys(), 2):
        delta = [x2 - x1 for x1,
                 x2 in zip(nodes[node1]["location"], nodes[node2]["location"])]
        distance = sqrt(sum(d ** 2 for d in delta))
        dist_mat[nodes_of_interest.index(
            node1), nodes_of_interest.index(node2)] = distance
        dist_mat[nodes_of_interest.index(
            node2), nodes_of_interest.index(node1)] = distance

    # 找到两个最远的结点
    [max_loc, temp] = np.where(dist_mat == dist_mat.max())
    [forward_node, reverse_node] = max_loc

    # create edges starting from the terminal nodes
    temp_edges = []
    forward_mat = dist_mat.copy()
    reverse_mat = dist_mat.copy()

    # walk through the network and makes temporary connections
    for i in range(0, len(nodes.keys())):
        next_node = forward_mat[forward_node, :].argsort()[1]
        forward_mat[forward_node, :] = dist_mat.max() + 1
        forward_mat[:, forward_node] = dist_mat.max() + 1
        temp_edges.append({"source": nodes_of_interest[
                forward_node], "target": nodes_of_interest[next_node]})
        forward_node = next_node

        next_node = reverse_mat[reverse_node, :].argsort()[1]
        reverse_mat[reverse_node, :] = dist_mat.max() + 1
        reverse_mat[:, reverse_node] = dist_mat.max() + 1
        temp_edges.append({"source": nodes_of_interest[
                reverse_node], "target": nodes_of_interest[next_node]})
        reverse_node = next_node

    nodes = run_forcing(temp_edges, nodes, iterations=force_iter)

    # update the x,y values for the forced nodes
    for node in nodes.keys():
        input_nodes[node]["location"][0:2] = nodes[node]["location"][0:2]

    return input_nodes


def _coulomb(n1, n2, k, r):
    """Calculates Coulomb forces and updates node data."""
    # Get relevant positional data
    delta = [x2 - x1 for x1, x2 in zip(n1["velocity"], n2["velocity"])]
    distance = sqrt(sum(d ** 2 for d in delta))

    # If the deltas are too small, use random values to keep things moving
    if distance < 0.1:
        delta = [uniform(0.1, 0.2) for _ in repeat(None, 3)]
        distance = sqrt(sum(d ** 2 for d in delta))

    # If the distance isn't huge (ie. Coulomb is negligible), calculate
    if distance < r:
        force = (k / distance) ** 2
        n1["force"] = [f - force * d for f, d in zip(n1["force"], delta)]
        n2["force"] = [f + force * d for f, d in zip(n2["force"], delta)]


def _hooke(n1, n2, k, r):
    """Calculates Hooke spring forces and updates node data."""
    # Get relevant positional data
    delta = [x2 - x1 for x1, x2 in zip(n1["velocity"], n2["velocity"])]
    distance = sqrt(sum(d ** 2 for d in delta))

    # If the deltas are too small, use random values to keep things moving
    if distance < 0.1:
        delta = [uniform(0.1, 0.2) for _ in repeat(None, 3)]
        distance = sqrt(sum(d ** 2 for d in delta))

    # Truncate distance so as to not have crazy springiness
    distance = min(distance, r)

    # Calculate Hooke force and update nodes
    force = (distance ** 2 - k ** 2) / (distance * k)
    n1["force"] = [f + force * d for f, d in zip(n1["force"], delta)]
    n2["force"] = [f - force * d for f, d in zip(n2["force"], delta)]


def _constrain(value, min_value, max_value):
    """Constrains a value to the inputted range."""
    return max(min_value, min(value, max_value))

# 颜色RGB指定
colors = { "red": (255, 0, 0), "orange": (255, 108, 0),
           "yellow": (204, 255, 0), "green": (0, 255, 90),
           "blue": (36, 182, 218), "indigo": (0, 18, 255),  
           "purple": (216, 0, 255), "gray": (188, 188, 188)}
          

# 将颜色标准化为[0,1]之间，并且设定materials
for key, value in colors.items():
    value = [x / 255.0 for x in value]
    bpy.data.materials.new(name=key)
    bpy.data.materials[key].diffuse_color = value
    bpy.data.materials[key].specular_intensity = 0.5

# 画出网图的主函数
def draw_network(network):
    """ Takes assembled network/molecule data and draws to blender """
    # 增加原始网格
    bpy.ops.object.select_all(action='DESELECT')
    bpy.ops.mesh.primitive_cube_add()
    cube = bpy.context.object

    # 保存所有节点和边的引用
    shapes = []

    # 生成结点
    for key, node in network["nodes"].items():

        # 结点的颜色设定
        col = node.get("color", choice(list(colors.keys())))

        # 复制原始网格并且生成新节点
        node_cube = cube.copy()
        node_cube.data = cube.data.copy()
        node_cube.name = key
        node_cube.location = node["location"]
        # node_cube.dimensions = [node_size] * 3
        node_cube.active_material = bpy.data.materials[col]
        bpy.context.scene.objects.link(node_cube)
        shapes.append(node_cube)
    
    for edge in network["edges"]:
        # 通过遍历获取源和目标的位置
        source_name = edge["source"]
        target_name = edge["target"]
        source_obj = bpy.data.objects[source_name]
        target_obj = bpy.data.objects[target_name]
        # 设置父子关系
        bpy.ops.object.mode_set(mode = 'OBJECT')
        bpy.ops.object.select_all(action='DESELECT')
        bpy.context.scene.objects.active = source_obj
        target_obj.select = True
        try:
            bpy.ops.object.parent_set()
        except:
            pass
   
    # 删除原始网格
    bpy.ops.object.select_all(action='DESELECT')
    cube.select = True

    # 删除启动时的小方块
    if "Cube" in bpy.data.objects.keys():
        bpy.data.objects.get("Cube").select = True
    bpy.ops.object.delete()

    # 将整个物体居中对齐
    bpy.ops.object.origin_set(type="ORIGIN_GEOMETRY", center="MEDIAN")

    # 刷新场景
    bpy.context.scene.update()

if __name__ == "__main__":
    # 用于spacing algorithm中给定层中的结点数目阈值，超过这个阈值，就要划分图层
    threshold_for_spacing = 20

    try:
        with open(r"json/res0.json") as in_file:
            edges = json.load(in_file)
    except IOError:
        with open(sys.argv[-1]) as in_file:
            edges = json.load(in_file)

    # 指定起始结点
    # starting_node = "4:System"

    # 转换为内部表示
    # edges = [{"source": str(s), "target": str(t)} for s, t in edges]
    for link in edges["links"]:
        # flag = False
        # if "explorer.exe" in link["source"] and flag == False:
        #     other_starting_node = link["source"]
        #     flag = True
        if link["target_group"] == 7:
            link["target"] = link["target"].replace("(", "")
            link["target"] = link["target"].replace(")", "")
            link["target"] = link["target"].replace("\'", "")
            new_target = link["target"].split(", ")[0]
            link["target"] = new_target
    edges = edges["links"]
    
    # 生成可用的结点，set集合，没有重复
    available_nodes = set(e["source"]
                          for e in edges) | set(e["target"] for e in edges)
    # 独立的根节点的集合
    roots = list(available_nodes)
    for n in available_nodes:
        for e in edges:
            if e["target"] == n:
                try:
                    roots.remove(n)
                except:
                    pass
    master_nodes = layer(edges, available_nodes, start_node=roots[0], depth=0)
    for i in range(1, len(roots)):
        available_nodes_backup = deepcopy(available_nodes)
        other_nodes = layer(edges, available_nodes_backup, start_node=roots[i], depth=0, xycoor=[60.0*i, 60.0*i])
        master_nodes = {**master_nodes, **other_nodes}
    # available_nodes_backup = deepcopy(available_nodes)
    # # 基于starting node生成最初的结点位置安排
    # master_nodes = layer(edges, available_nodes,
    #                      start_node=starting_node, depth=0)
    
    # # 获取每个图层的深度和计数的范围
    # depth_count = {}
    # for node in master_nodes.keys():
    #     if master_nodes[node]["depth"] in depth_count.keys():
    #         depth_count[master_nodes[node]["depth"]] = depth_count[
    #             master_nodes[node]["depth"]] + 1
    #     else:
    #         depth_count[master_nodes[node]["depth"]] = 1

    # # 根据特定图层中有多少结点来划分图层
    # for layer_of_interest, count in depth_count.items():
    #     if count > threshold_for_spacing:
    #         nodes_of_interest = []
    #         for node in master_nodes.keys():
    #             if master_nodes[node]["depth"] == layer_of_interest:
    #                 nodes_of_interest.append(node)

    #         master_nodes = space(
    #             master_nodes, nodes_of_interest, force_iter=10)
    
    # 转换成json
    # if len(master_nodes) < len(available_nodes_backup):
    #     other_nodes = layer(edges, available_nodes_backup, start_node=other_starting_node, depth=0, xycoor=[80.0, 80.0])
    #     new_master_nodes = {**master_nodes, **other_nodes}
    #     json_str = dumps({"edges": edges, "nodes": new_master_nodes})
    # else:
    #     json_str = dumps({"edges": edges, "nodes": master_nodes})
    json_str = dumps({"edges": edges, "nodes": master_nodes})
    network = json.loads(json_str)

    # 添加add_color
    name_group = {}
    for edge in network['edges']:
        if edge['source'] not in name_group.keys():
            name_group[edge['source']] = edge['source_group']
        if edge['target'] not in name_group.keys():
            name_group[edge['target']] = edge['target_group']
    
    for key in network['nodes'].keys():
        if name_group[key] == 1:
            network['nodes'][key]['color'] = 'blue'
        elif name_group[key] == 2:
            network['nodes'][key]['color'] = 'yellow'
        elif name_group[key] == 3:
            network['nodes'][key]['color'] = 'green'
        elif name_group[key] == 4:
            network['nodes'][key]['color'] = 'indigo'
        elif name_group[key] == 5:
            network['nodes'][key]['color'] = 'orange'
        elif name_group[key] == 6:
            network['nodes'][key]['color'] = 'purple'
        elif name_group[key] == 7:
            network['nodes'][key]['color'] = 'gray'
    network_copy = deepcopy(network)
    networks.append(network_copy)
    draw_network(network)
    
    try:
        unregister()
    except Exception as e:
        print(e)
        pass
    finally:
        register()